#!/usr/bin/env python
# encoding: utf-8


"""
@file: hancanleixing.py
@time: 2016/12/30 下午5:31
"""
from functools import reduce

# 含参类型的线性规划题型
from mathsolver.functions.base import *
from mathsolver.functions.budengshi import common_opers as co
from operator import itemgetter
from mathsolver.functions.budengshi.junzhibudengshi.junzhibudengshi import JunZhiBuDengShi
from sympy import symbols, solveset
from mathsolver.functions.budengshi.linear_pro import XianXingGuiHua
from itertools import combinations, chain


# 不等式组不含参，目标函数含参的
# input: paramer1:ineqs; paramer2:eq; paramer3: baseValue; paramer4: text
# output: eq 关于参数的等式
# 若实数x,y满足不等式组\\left\\{\\begin{array}{c}3x-y≤6\\\\x-y≥-2\\\\x≥0\\\\y≥0\\end{array}\\right.,
# 目标函数z=ax+by(a>0,b>0)的最大值为12,则\\frac{2}{a}+\\frac{3}{b}的最小值为().
class HanCan001(BaseFunction):
    @staticmethod
    def symbol_positived(f):
        tmp_f = f
        symbs = list(f.free_symbols)
        for symb in symbs:
            tmp_symb = Symbol(str(symb), positive=True)
            tmp_f = tmp_f.subs(symb, tmp_symb)
        return tmp_f

    def solver(self, *args):
        self.label.add('含参线性规划')
        ineqs = args[0]
        arg2 = args[1]
        arg3 = args[2]
        arg4 = args[3]
        op_value = arg3.sympify()
        if isinstance(arg2, BaseEq):
            z_symbol, targt_expr = arg2.sympify()
        elif isinstance(arg2, BaseEq):
            targt_expr = arg2.sympify()
        ineqs_list = co.simp_ineqs(ineqs)
        inter_points = co.satisfied_ineqs_intersections(ineqs_list)  # 交点
        op_text = '最大值' if arg4.find('最大值') >= 0 else '最小值'
        op_func = max if op_text == '最大值' else min
        inter_exprs = map(lambda p: (p[0], HanCan001.symbol_positived(p[1])),
                          map(lambda p: (p[0], targt_expr.subs(p[1])), inter_points))
        op_inter_expr = op_func(inter_exprs, key=itemgetter(1))
        self.steps.append(['不等式表示的平面区域如图所示阴影部门，', ''])
        line1 = BaseEq(op_inter_expr[0][0][::2])
        line2 = BaseEq(op_inter_expr[0][1][::2])
        inter_point = list(zip(*co.solve_poly_eqs(BaseEqs([line1.sympify(), line2.sympify()]))[0]))[1]
        self.steps.append(['当直线' + arg2.printing() + ' 过直线%s与直线%s的交点' % (line1.printing(), line2.printing()),
                           '(%s, %s)时' % (new_latex(inter_point[0]), new_latex(inter_point[1]))])
        self.steps.append(['目标函数' + arg2.printing(), '取得最大值' + arg3.printing()])
        self.steps.append(['即' + BaseEq([op_inter_expr[1], op_value]).printing(), ''])
        param_eq = BaseEq([str(op_inter_expr[1]), op_value])
        if len(args) == 5:  # 如果含有5个参数，要求最后一个表达式的最值
            # self.output.append(param_eq)
            # 利用均值不等式
            op_solve = JunZhiBuDengShi(verbose=True).solver('正数', param_eq, args[4])
            self.steps.extend(op_solve.steps)
            self.output.append(op_solve.output[0])
            return self
        else:
            raise Exception('Type Match Error')


# 15470949
# 若实数x,y满足\\left\\{\\begin{array}{c}2x-y≥0\\\\y≥x\\\\y≥-x+b\\end{array}\\right.,z=2x+y的最小值为4,则实数b的值为().
# input: paramer1:ineqs; paramer2:eq; paramer3: baseValue; paramer4: text
class HanCan002(BaseFunction):
    def solver(self, *args):
        self.label.add('含参线性规划')
        ineqs = args[0]
        arg2 = args[1]
        arg3 = args[2]
        arg4 = args[3]
        op_value = arg3.sympify()
        x_symbol, y_symbol = symbols('x, y')
        if isinstance(arg2, BaseEq):
            z_symbol, targt_expr = arg2.sympify()
        elif isinstance(arg2, BaseEq):
            targt_expr = arg2.sympify()
        else:
            raise Exception('Illegal parameter')
        targt_expr = sympify(str(targt_expr))
        ineqs_list = co.simp_ineqs(ineqs)
        all_symbols = chain(co.eqs_symbs([['0', targt_expr]]), co.ineqs_symbs(ineqs.sympify()))
        param_symbol = sympify(str(list(set(map(sympify, all_symbols)) - {x_symbol, y_symbol})[0]))
        # param_symbol = list(target_expr_param_symb)[0] if target_expr_param_symb else list(ineqs_param_symb)[0]
        param_values = []  # 参数的可能值
        op_text = '最大值' if arg4.find('最大值') >= 0 else '最小值'
        # ---Step1 求交点
        inter_points = co.ineqs_intersections(ineqs_list)  # 求两两直线的交点
        if len(inter_points) == 1:  # 两条直线 只有一个交点的时候 ID:25228797
            inter_point = inter_points[0][1]
            target_expr_value = targt_expr.subs(inter_point)
            tmp_param_values = solve(target_expr_value - op_value)
            line_left, line_right = sympify(inter_points[0][0][0][0]), sympify(inter_points[0][0][0][2])
            tmp_line_expr = line_left - line_right
            for tmp_param_value in tmp_param_values:
                tmp_ineqs_list = list(map(lambda tmp_ineq: [tmp_ineq[0].subs(param_symbol, tmp_param_value), tmp_ineq[1], tmp_ineq[2].subs(param_symbol, tmp_param_value)], ineqs_list.sympify()))
                tmp_line = tmp_line_expr.subs(param_symbol, tmp_param_value)
                back_inter_point_x = dict(inter_point)[x_symbol] - 1
                back_inter_point_y = solve(tmp_line.subs(x_symbol, back_inter_point_x), y_symbol)[0]
                back_inter_point = list(map(lambda point_v: point_v.subs(param_symbol, tmp_param_value), (back_inter_point_x, back_inter_point_y)))
                prev_inter_point_x = dict(inter_point)[x_symbol] + 1
                prev_inter_point_y = solve(tmp_line.subs(x_symbol, prev_inter_point_x), y_symbol)[0]
                prev_inter_point = list(map(lambda point_v: point_v.subs(param_symbol, tmp_param_value), (prev_inter_point_x, prev_inter_point_y)))
                back_inter_point_intls = list(map(lambda ineq: solveset(''.join([str(ineq[0].subs(zip((x_symbol, y_symbol), back_inter_point))), ineq[1], str(ineq[2].subs(zip((x_symbol, y_symbol), back_inter_point)))]), domain=S.Reals), tmp_ineqs_list))
                valiate_other_point = back_inter_point if co.intervals_intersect(back_inter_point_intls) else prev_inter_point
                targt_expr_value = targt_expr.subs(param_symbol, tmp_param_value).subs(zip((x_symbol, y_symbol), valiate_other_point))
                if op_text.find('最大') >= 0 and targt_expr_value <= op_value:
                    param_values.append(tmp_param_value)
                elif op_text.find('最小值') >= 0 and targt_expr_value >= op_value:
                    param_values.append(tmp_param_value)
            param_values_print = reduce(lambda _1, _2: _1 + '或' + _2, map(lambda _: new_latex(_), param_values))
            self.steps.append(['解得:%s = %s' % (new_latex(param_symbol), param_values_print), ''])
            self.output.append(BaseSymbolValue({param_symbol: param_values}))
            return self
        # ---Step2 判断交点，分母是否为0
        for i_p in inter_points:
            i_p_v = i_p[1]
            i_p_x_v = i_p_v[0][1]
            x_v_de = co.complete_fraction(i_p_x_v)[1]
            if not x_v_de.is_real:
                tmp_param_value = solve(x_v_de)[0]
                tmp_ineqs_list = map(
                    lambda tmp_ineq: [tmp_ineq[0].subs(param_symbol, tmp_param_value), tmp_ineq[1],
                                      tmp_ineq[2].subs(param_symbol, tmp_param_value)],
                    ineqs_list.sympify())
                tmp_expr = targt_expr.subs(param_symbol, tmp_param_value)
                tmp_op_value = XianXingGuiHua(verbose=True).solver(BaseIneqs(tmp_ineqs_list), BasePoly(tmp_expr),
                                                                   op_text).output[0].sympify()
                print(tmp_op_value)
        # ---Step3 判断交点是否在可行域内。把每个交点带入不等式里面去看看不等式组是否
        inter_point_intls = []
        for i_p in inter_points:
            i_p_v = i_p[1]
            i_p_x_v = i_p_v[0][1]
            i_p_y_v = i_p_v[1][1]
            tmp_subs = ((x_symbol, i_p_x_v), (y_symbol, i_p_y_v))
            tmp_intls = map(
                lambda tmp_ineq: solveset(
                    ''.join([str(tmp_ineq[0].subs(tmp_subs)), tmp_ineq[1], str(tmp_ineq[2].subs(tmp_subs))]),
                    domain=S.Reals),
                ineqs_list.sympify())
            tmp_last_intl = co.intervals_intersect(tmp_intls)
            inter_point_intls.append((i_p, tmp_last_intl))
        all_segs = []
        for i in range(1, len(inter_point_intls) + 1):
            tmp_seg = list(combinations(inter_point_intls, i))
            all_segs.extend(tmp_seg)
        valiate_segs = []
        for seg in all_segs:
            tmp_intl = S.Reals
            tmp_inters = []
            for inter_intl in seg:
                tmp_intl = tmp_intl.intersect(inter_intl[1])
                tmp_inters.append(inter_intl[0])
            if tmp_intl:
                valiate_segs.append((tmp_intl, tmp_inters))
        param_intl_inters = {}
        for tmp_seg_intl, tmp_seg_inters in valiate_segs:
            current_inters = param_intl_inters.get(tmp_seg_intl, [])
            current_inters.extend(tmp_seg_inters)
            param_intl_inters[tmp_seg_intl] = current_inters
        for tmp_intl, tmp_inters in param_intl_inters.items():
            param_intl_inters[tmp_intl] = dict(tmp_inters).items()
        ineq_op = "<=" if op_text.find('最大') >= 0 else '>='
        # --- Step 分情况讨论
        for tmp_intl, tmp_inters in param_intl_inters.items():
            for current_op_inter in tmp_inters:
                other_inters = filter(lambda inter: inter[0] != current_op_inter[0], tmp_inters)
                targt_expr_value = targt_expr.subs(current_op_inter[1])
                if targt_expr_value.is_real and targt_expr_value == op_value:
                    if other_inters:
                        other_inter_intls = map(
                            lambda inter: solveset(str(targt_expr.subs(inter[1])) + ineq_op + str(op_value)),
                            other_inters)
                        intersect_intl = co.intervals_intersect(other_inter_intls)
                        if intersect_intl.intersect(tmp_intl):
                            param_values.append(intersect_intl.intersect(tmp_intl))
                    else:
                        param_values.append(tmp_intl)
                elif not targt_expr_value.is_real:
                    # tmp_param_values = solve(targt_expr_value - op_value)
                    tmp_param_values = co.isolve_eq2(BaseEq([targt_expr_value, op_value]))
                    for tmp_param_value in tmp_param_values:
                        tmp_ineqs_list = map(
                            lambda tmp_ineq: [tmp_ineq[0].subs(param_symbol, tmp_param_value), tmp_ineq[1],
                                              tmp_ineq[2].subs(param_symbol, tmp_param_value)],
                            ineqs_list.sympify())
                        tmp_expr = targt_expr.subs(str(param_symbol), tmp_param_value)
                        try:
                            tmp_op_value = XianXingGuiHua(verbose=True).solver(BaseIneqs(tmp_ineqs_list),
                                                                               BasePoly(tmp_expr),
                                                                               op_text).output[0].sympify()
                        except Exception:
                            tmp_op_value = None
                        if isinstance(tmp_op_value, dict):
                            tmp_op_value = tmp_op_value.values()[0]
                            if isinstance(tmp_op_value, FiniteSet):
                                tmp_op_value = list(tmp_op_value)[0]
                        if tmp_param_value in tmp_intl and tmp_op_value == op_value:
                            param_values.append(tmp_param_value)
        param_values = list(set(param_values))
        param_intl = self.search(param_symbol)
        if param_intl and isinstance(param_intl, Interval):
            param_values = filter(lambda _: _ in param_intl, param_values)
        if len(param_values) == 1:
            self.steps.append(['解得:%s = %s' % (new_latex(param_symbol), new_latex(param_values[0])), ''])
        else:
            param_values_print = reduce(lambda _1, _2: _1 + '或' + _2, map(lambda _: new_latex(_), param_values))
            self.steps.append(['解得:%s = %s' % (new_latex(param_symbol), param_values_print), ''])
        self.output.append(BaseSymbolValue({param_symbol: param_values}))
        return self


class HanCanXianXingGuiHua(BaseFunction):
    CLS = [HanCan001, HanCan002]

    def solver(self, *args):
        solve_result = None
        for cl in HanCanXianXingGuiHua.CLS:
            try:
                solve_result = cl(verbose=True).solver(*args)
                break
            except Exception:
                pass
        if not solve_result:
            raise Exception('Can not solve the question')
        return solve_result


if __name__ == '__main__':
    # r = HanCanXianXingGuiHua(verbose=True).solver(
    #     BaseIneqs([['3*x - y', '<=', '6'], ['x - y', '>=', '-2'], ['x', '>=', '0'], ['y', '>=', '0']]),
    #     BaseEq(['z', 'a*x + b*y']),
    #     BaseNumber('12'), '最大值', BasePoly('2/a + 3/b'))
    # for step1, step2 in r.steps:
    #     print step1, step2

    # r = HanCanXianXingGuiHua(verbose=True).solver(
    #     BaseIneqs([['2*x - y', '>=', '0'], ['y', '>=', 'x'], ['y', '>=', '-x+b']]),
    #     BaseEq(['z', '2*x + y']),
    #     BaseNumber('4'), '最小值')
    # r = HanCanXianXingGuiHua(verbose=True).solver(
    #     BaseIneqs([['x + y', '>=', 'a'], ['x - y', '<=', '-1']]),
    #     BaseEq(['z', 'x + a*y']),
    #     BaseNumber('7'), '最小值')
    # r = HanCanXianXingGuiHua(verbose=True).solver(
    #     BaseIneqs([['x', '>=', '1'], ['x + y', '<=', '3'], ['y', '>=', 'a*(x-3)']]),
    #     BaseEq(['z', '2*x + y']),
    #     BaseNumber('1'), '最小值')
    # r = HanCanXianXingGuiHua(verbose=True).solver(
    #     BaseIneqs([['x - y', '>=', '0'], ['x + y', '<=', '2'], ['y', '>=', '0']]),
    #     BaseEq(['z', 'a*x + y']),
    #     BaseNumber('4'), '最大值')
    r = HanCanXianXingGuiHua(verbose=True).solver(
        BaseIneqs([['x - y', '>=', '0'], ['x + y', '<=', '2'], ['y', '>=', '0']]),
        BaseEq(['z', 'a*x + y']), BaseNumber('4'), '最大值')
